iT邦幫忙

2025 iThome 鐵人賽

DAY 11
2
Modern Web

Angular 進階實務 30天系列 第 11

Day 11:Router - 參數傳遞

  • 分享至 

  • xImage
  •  

在實務上有非常多種類的資料流設計,我會以我工作上遇到的經驗來分享 Router 能夠處理的參數傳遞。

Angular Router 有幾種主要的參數傳遞形式,分別有:Route Parameters (路由參數)、Query Parameters (查詢參數)、Fragment (錨點)、State (狀態傳遞)、 Data (靜態資料)、Resolve (解析器資料)

1. Route Parameters 路由參數

這是很常用到的傳遞形式 'user/:id’,通常用於傳遞必要的識別資訊。

實務上在編輯指定資料、或是檢視指定資料的時候會用到,在點擊的時候一併傳遞id,並且呼叫API利用ID取得資料,或是過濾列表取得資料。

如果在URL上有帶id,代表這頁資料重新整理之後,還可以透過URL上的id重新撈取資料,如果重新整理之後,原本正在查看的明細消失跳回列表頁,其實不是很好的使用者體驗 。

此外,如果筆數資料量比較大,就請後端提供取得單筆的API,或是單筆裡面包含的資料量就很大,像是保單明細或是交易明細,畫面上不會顯示所有的資料,API卻全部給你的話,也是用了多餘的傳輸量,當然也是看實際狀況,有時候少的可以忽略不計。

但還是得說最好還是透過API取得,雖然可能會有人跟你說沒辦法開給你,但是請記得最好的做法還是透過id取得單筆的資料,不然重新整理之後要呼叫 列表API,又要再透過id跑一次迴圈找那筆,算是沒有意義的消耗。

💡提醒
取得單筆跟列表的API,除特殊狀況外,應該分開提供

// 路由配置
{ path: 'user/:id', component: UserComponent }

// 導航
this.router.navigate(['/user', 123]);

// 接收參數
constructor(private route: ActivatedRoute) {
  this.route.params.subscribe(params => {
    const id = params['id'];
  });
}

2. Query Parameters 查詢參數

在 URL 後面以 ?key=value 形式傳遞,適合可選參數
舉一個使用情境跟大家分享:當我在列表頁,查詢條件設定了條件A、B找到了甲資料後,我開始編輯甲資料,編輯完甲資料後,跳轉回列表頁時,我想要看到原本查詢的資料結果,也直接看到甲資料,這時候 Query Parameters 就會帶當初的條件A、B跟頁碼,不過這個可能會有遇到同時有人新增的問題,甲資料可能不在原頁,但是使用者表示這樣 ok,因為機會很小。或是直接帶回這筆的識別資料,回列表頁查詢,也是一個做法。

// 導航時傳遞
this.router.navigate(['/products'], { 
  queryParams: { category: 'electronics', page: 1 } 
});

// URL: /products?category=electronics&page=1

// 接收參數
this.route.queryParams.subscribe(params => {
  const category = params['category'];
  const page = params['page'];
});

3. Fragment 錨點

這與傳統的分頁導航用法不同,通常是用在長畫面內容的網頁上,當需要跳到頁面指定位置的時候會使用,像是一頁式網站、LandingPage、產品或個人介紹、也會作為 FAQ 跳到頁面指定位置用

// 導航
this.router.navigate(['/article'], { fragment: 'section1' });

// URL: /article#section1

// 接收
this.route.fragment.subscribe(fragment => {
  console.log(fragment); // 'section1'
});

Single Page Application (SPA) vs 一頁式網站

我不確定有沒有人納悶過這件事,但是我有

雖然我理解實作上完全不一樣,可是在中文上都是一頁,所以讓我困惑過

他們是分別屬於兩種不同領域的用詞

  • 技術架構層面:SPA (Single Page Application)
  • 視覺設計層面:一頁式網站 (One Page Website

關鍵差異對比

特性 SPA 一頁式網站
頁面數量 多個邏輯頁面 單一視覺頁面
URL 變化 /home, /products, /about /#hero, /#services, /#about
內容載入 按需載入不同 Component 所有內容一次載入
導航方式 路由切換 頁面內滾動/跳轉
使用場景 複雜應用程式 簡單展示網站

4. State 狀態傳遞

透過 NavigationExtrasstate 屬性傳遞複雜物件,不會顯示在 URL 中。

Angular Router 的 State 資料儲存在瀏覽器的記憶體中,具體來說是存放在瀏覽器的 History API 狀態物件內。這個儲存機制有幾個重要特性。

儲存位置與機制

State 資料透過瀏覽器原生的 history.pushState()history.replaceState() API 儲存在當前頁面的歷史記錄項目中。當您使用 Angular Router 的 state 參數時,資料會被序列化並與該特定的歷史記錄項目關聯。

typescript

// Angular 內部會呼叫類似這樣的瀏覽器 API
history.pushState({ userData: { name: 'John', age: 30 } }, '', '/target');

資料的生命週期限制

State 資料存在幾個重要的限制條件。首先,資料僅在當前瀏覽器會話期間有效,一旦用戶關閉瀏覽器分頁或視窗,這些資料就會遺失。其次,資料無法跨瀏覽器分頁或視窗共享,每個分頁都有獨立的導航狀態。

最重要的是,State 資料與特定的歷史記錄項目綁定。當用戶使用瀏覽器的前進或後退按鈕時,會回到對應歷史記錄的狀態,但如果重新整理頁面或直接輸入網址,State 資料就會消失。

意思就是沒有做其他處理的話,網頁重新整理之後,資料沒了,通常是敏感性高的資料會要求不顯示在URL上傳遞,像是身分證或是Token類的,或是它有比較大量的資料需要傳遞,因為 Query Parameters 可能會遇到長度限制(通常是2,048字元)。

// 導航時傳遞
this.router.navigateByUrl('/target', { 
  state: { userData: { name: 'John', age: 30 } } 
});

// 接收
constructor(private router: Router) {
  const navigation = this.router.getCurrentNavigation();
  const state = navigation?.extras.state;
  console.log(state?.userData);
}

5. Data 靜態資料

在路由配置中預先定義的靜態資料。

實務經驗中我是在處理新增跟修改元件的時候會用到,因為我通常會設計成同一個元件,這時候新增跟修改的路徑是固定不變的,就直接定義資料傳入做識別。

// 路由配置
{
    path: DOCUMENT_ROUTES.PERSONAL_TEMPLATES.SYSTEM_FORM.DEFAULT,
    component: TemplateListBaseComponent,
    data: { title: '申請單', templateType: FormType.A01, buttonText: '新增個人模板' }
},
{
    path: `${DOCUMENT_ROUTES.PERSONAL_TEMPLATES.SYSTEM_FORM.DEFAULT}/${DOCUMENT_ROUTES.PERSONAL_TEMPLATES.SYSTEM_FORM.CREATE}`,
	  component: SystemFormTemplateComponent,
    data: { templateType: FormType.A01, isEditMode: false }

},
{
    path: `${DOCUMENT_ROUTES.PERSONAL_TEMPLATES.SYSTEM_FORM.DEFAULT}/${DOCUMENT_ROUTES.PERSONAL_TEMPLATES.SYSTEM_FORM.EDIT}/:id`,
    component: SystemFormTemplateComponent,
    data: { templateType: FormType.A01, isEditMode: true }
}

// 接收
this.route.data.subscribe(data => {
  const isEditMode = data['isEditMode'];
});

6. Resolve (解析器資料)

透過解析器在路由觸發前預先載入資料,需要做初始化的資料都可以考慮使用。

編輯頁的時候通常需要先顯示前一次的資料,之後再做調整,這時候就很適合使用Resolve來處理這個固定需要預處理的部分。

另外如果說要取得一些全域性的資料,或是做權限、角色的檢查,也會在這裡處理,這樣可以在頁面載入前就完成檢查,沒有權限的人會被導向到登入頁面或是其他不需要權限的頁面,這樣可以避免頁面閃爍的不良體驗(先顯示內容一下,之後驗證完發現不對又被踢出去)

// 解析器
// 載入當前登入用戶資料
@Injectable()
export class CurrentUserResolver implements Resolve<User> {
  resolve(): Observable<User> {
    // 從 token 或 session 中取得當前用戶 ID
    return this.authService.getCurrentUser();
  }
}

// 載入系統設定
@Injectable()
export class ConfigResolver implements Resolve<Config> {
  resolve(): Observable<Config> {
    return this.configService.getSystemConfig();
  }
}

// 路由配置
{ 
  path: 'profile', 
  component: ProfileComponent,
  resolve: { user: CurrentUserResolver, sysConfig: ConfigResolver  }
}

// 接收
this.route.data.subscribe(data => {
  const user = data['user'];
  const sysConfig = data['sysConfig']
});

跟其他路由參數傳遞方法合作,取得需要的資料

// 路由配置
{
  path: 'user/:id',
  component: UserProfileComponent,
  resolve: { user: UserResolver }
}

// UserResolver 實作
@Injectable()
export class UserResolver implements Resolve<User> {
  constructor(private userService: UserService) {}
  
  resolve(route: ActivatedRouteSnapshot): Observable<User> {
    const userId = route.params['id']; // 從路由參數取得 ID
    return this.userService.getUserById(userId);
  }
}

// 導航時傳遞參數
this.router.navigate(['/user', 123]);
// URL: /user/123,Resolver 會拿到 id=123 去載入資料

總結表格

傳遞方式 語法範例 URL 顯示 適用場景 生命週期 取得方式
Route Parameters路由參數 { path: 'user/:id' }navigate(['/user', 123]) /user/123 • 必要的識別資訊• RESTful API 路徑• 可書籤化資源 與 URL 同步重新整理不會遺失 route.params.subscribe()
Query Parameters查詢參數 navigate(['/products'], {queryParams: { page: 1 } }) /products?page=1 • 可選的篩選條件• 分頁資訊• 搜尋條件 與 URL 同步重新整理不會遺失 route.queryParams.subscribe()
Fragment錨點 navigate(['/article'], {fragment: 'section1' }) /article#section1 • 頁面內導航• 長頁面定位• 分享特定段落 與 URL 同步重新整理不會遺失 route.fragment.subscribe()
State狀態傳遞 navigateByUrl('/target', {state: { userData: {...} } }) URL 中不顯示 • 複雜物件傳遞• 敏感資訊• 暫時性資料 僅限當前導航會話重新整理會遺失 router.getCurrentNavigation()?.extras.state
Data靜態資料 { path: 'about',data: { title: 'About' } } URL 中不顯示 • 路由配置資料• 麵包屑資訊• 頁面標題 與路由配置同步靜態不變 route.data.subscribe()
Resolve解析器資料 { path: 'profile',resolve: { user: UserResolver } } URL 中不顯示 • 頁面進入前預載入資料• 可搭配權限驗證• 確保資料完整性 路由進入前觸發路由參數變更時會重新觸發 route.data.subscribe()

上一篇
Day 10:Router 基礎設計:單層與巢狀
下一篇
Day 12:Router - 從點擊到頁面載入的完整生命週期
系列文
Angular 進階實務 30天22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言